package ipms.snmp;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Vector;
import java.util.Map.Entry;

import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;

/**
 * 
 * @author spdjnh
 * @created on 2012.12.28 
 * 定义Snmp Engine,负责维护基本的Snmp连接 
 * 支持v1、v2版的同步get、set方法
 * 
 */
public class SnmpV2 {
	private Snmp snmp;
	private Target target;

	private Address address;
	private OctetString community = new OctetString("public");
	private int version = SnmpConstants.version2c;

	private int retries = 0;
	private int timeout = 1000;
	private int maxSizeResponsePDU = 65535;

	public SnmpV2(String address, String community) throws Exception {
		this(address, community, 1, 0, 1000);
	}

	public SnmpV2(String address, String community, Integer retries, Integer timeout)
			throws Exception {
		this(address, community, 1, retries, timeout);
	}

	public SnmpV2(String address, String community, Integer version,
			Integer retries, Integer timeout) throws Exception {
		super();
		this.address = SnmpBase.getAddress(address);
		this.community = SnmpBase.createOctetString(community);
		this.version = version;
		this.retries = retries;
		this.timeout = timeout;

		snmpInitial();
	}

	public Target getTarget() {
		return target;
	}

	public void setTarget(Target target) {
		this.target = target;
	}

	// ///////////////////////////////////////////////////////////////

	private void snmpInitial() throws IOException {
		// 建立Snmp对象
		this.snmp = SnmpBase.createSnmpSession(address);

		// 建立Target对象
		this.target = SnmpBase.createTarget(address, community);

		target.setVersion(version);
		target.setRetries(retries);
		target.setTimeout(timeout);
		target.setMaxSizeRequestPDU(maxSizeResponsePDU);

		snmp.listen();
	}

	public void close() throws IOException {
		if (null != snmp) {
			snmp.close();
			snmp = null;
		}
	}

	@Override
	public void finalize() {
		try {
			close();
		} catch (IOException e) {

		}
	}

	public ResponseEvent sendRequest(PDU request) throws IOException {
		return snmp.send(request, this.target);
	}

	// ////////////////////////////////////////////////////////////////////

	/**
	 * 获取单个oid对应的值
	 * 
	 * @param oid
	 * @return PDU
	 * @throws IOException
	 */
	public PDU get(String oid) throws IOException {
		return get(new OID(oid));
	}
	
	public PDU get(OID oid) throws IOException {
		PDU pdu = null;

		// 建立PDU对象
		PDU request = SnmpBase.createPDU(PDU.GET);

		request.add(new VariableBinding(oid));

		// 发起snmp request
		ResponseEvent respEvent = sendRequest(request);

		pdu = respEvent.getResponse();
		if (pdu == null) {
			// request timed out
			throw new IOException("Time out when get '" + oid.toString() + "' from "
					+ address.toString());
		}

		return pdu;
	}

	/**
	 * 获取多个oid对应的值
	 * 
	 * @param vbs[]
	 * @return PDU
	 * @throws IOException
	 */
	public PDU get(VariableBinding vbs[]) throws IOException {
		PDU pdu = null;
		// 建立PDU对象
		PDU request = SnmpBase.createPDU(PDU.GET);

		request.addAll(vbs);

		// 发起snmp request
		ResponseEvent respEvent = sendRequest(request);

		pdu = respEvent.getResponse();
		if (pdu == null) {
			// request timed out
			throw new IOException("Time out when get from "
					+ address.toString());
		}

		return pdu;
	}

	/**
	 * 获取下一可访问节点的值
	 * 
	 * @param oid
	 * @return PDU
	 * @throws IOException
	 */
	public PDU getNext(String preOid) throws IOException {
		return getNext(new OID(preOid));
	}
	
	public PDU getNext(OID preOid) throws IOException {
		PDU pdu = null;

		// 建立PDU对象
		PDU request = SnmpBase.createPDU(PDU.GETNEXT);

		request.add(new VariableBinding(preOid));

		// 发起snmp request
		ResponseEvent respEvent = sendRequest(request);

		pdu = respEvent.getResponse();
		if (pdu == null) {
			// request timed out
			throw new IOException("Time out when get next '" + preOid
					+ "' from " + address.toString());
		}

		return pdu;
	}

	/**
	 * 针对一个oid去获取其下所有可访问子节点的值
	 * 
	 * @param baseOid
	 * @return ArrayList<PDU>
	 * @throws IOException
	 */
	public ArrayList<PDU> walk(String baseOid) throws IOException {
		return walk(new OID(baseOid));
	}
	
	public ArrayList<PDU> walk(OID baseOid) throws IOException {
		ArrayList<PDU> arrPdu = new ArrayList<PDU>();

		OID currentOid = baseOid;

		while (true) {
			// 建立PDU对象
			PDU request = SnmpBase.createPDU(PDU.GETNEXT);
			// 用currentOid 发snmp request
			request.add(new VariableBinding(new OID(currentOid)));

			// 发起snmp request
			ResponseEvent respEvent = sendRequest(request);

			PDU pdu = null;
			pdu = respEvent.getResponse();

			if (pdu == null) {
				// request timed out
				throw new IOException("Time out when walk on '" + baseOid.toString()
						+ "' from " + address.toString());
			} else {
				VariableBinding vb = null;
				@SuppressWarnings("unchecked")
				Vector<VariableBinding> vvb = (Vector<VariableBinding>) pdu
						.getVariableBindings();

				if (1 == vvb.size()) {
					vb = vvb.elementAt(0);
					// snmp request获取的结果，再赋值给currentOid,形成类似递归的效果
					currentOid = vb.getOid();
					if (currentOid.startsWith(baseOid)) {
						arrPdu.add(pdu);
					} else {
						// 如果获取的oid不再startsWith baseOid，就终止循环
						break;
					}
				}
			}

		}

		return arrPdu;
	}

	
	/**
	 * 获取Table节点，类似mib browser的table view
	 * 
	 * @param entry：参见MibConst.java中的定义方式
	 * @return：每一行的“instance”key可以取出实例（或indexes），其值为OID类型
	 * 			如：1.2.1
	 * @throws IOException
	 */
	public  ArrayList<HashMap<String, Variable>> getTable(HashMap<String, OID> entry) throws IOException {
		
		ArrayList<HashMap<String, Variable>> rows = new ArrayList<HashMap<String, Variable>>();
		int[] Indexes = null;

		boolean bfirst = true;
		try {
			for (Entry<String, OID> en : entry.entrySet()) {
				ArrayList<PDU> arPdu = this.walk(en.getValue());
				int irows = arPdu.size();
				if(irows>0 && bfirst) //获取第一列的值
				{
					for(PDU pdu : arPdu) {
						HashMap<String, Variable> row = new HashMap<String, Variable>();
						
						//添加索引，将其存为OID形式
						int[] arr1 = pdu.getVariableBindings().get(0).getOid().getValue();
						int[] arr2 = en.getValue().getValue();
						Indexes = new int[arr1.length-arr2.length];
						System.arraycopy(arr1, arr2.length, Indexes,0,Indexes.length);
						row.put("instance", new OID(Indexes));
						
						row.put(en.getKey(), pdu.getVariableBindings().get(0).getVariable());
						rows.add(row);
					}
					bfirst = false;
					
				} else {
					for(int i=0; i<irows ; i++) {
						//取出先前存放的列值
						HashMap<String, Variable> row = (HashMap<String, Variable>)rows.get(i);
						
						//加入新的列值
						row.put(en.getKey(), arPdu.get(i).getVariableBindings().get(0).getVariable());
					}
				}
			}
		} catch (IOException e) {
			throw e;
		} 
		
		return rows;
	}
	
	/**
	 * 设置单个节点
	 * 
	 * @param vb
	 * @return PDU
	 * @throws IOException
	 */
	public PDU set(VariableBinding vb) throws IOException {
		PDU pdu = null;

		// 建立PDU对象
		PDU request = SnmpBase.createPDU(PDU.SET);

		request.add(vb);

		// 发起snmp request
		ResponseEvent respEvent = sendRequest(request);

		pdu = respEvent.getResponse();
		if (pdu == null) {
			// request timed out
			throw new IOException("Time out when set to " + address.toString());
		}

		return pdu;
	}

	/**
	 * 设置多个节点
	 * 
	 * @param vbs[]
	 * @return PDU
	 * @throws IOException
	 */
	public PDU set(VariableBinding vbs[]) throws IOException {
		PDU pdu = null;

		// 建立PDU对象
		PDU request = SnmpBase.createPDU(PDU.SET);

		request.addAll(vbs);

		// 发起snmp request
		ResponseEvent respEvent = sendRequest(request);

		pdu = respEvent.getResponse();
		if (pdu == null) {
			// request timed out
			throw new IOException("Time out when set to " + address.toString());
		}

		return pdu;
	}
}
